package com.gframework.mybatis.dao.mybatis.plugins;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectionException;
import org.apache.ibatis.session.RowBounds;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import com.gframework.util.GStringUtils;

/**
 * 懒加载删除数据处理类.<br>
 * 本类中的相关数据（需要通过数据库查询的数据），只会在调用的时候才会进行加载。<br>
 * <p>
 * <strong>安全隐患：</strong>
 * 本类是线程不安全的，所以每次都要实例化一个新的本类对象进行处理。同时也不要异步的同时调用一个对象的多个方法，会造成参数错乱导致冲突。
 * </p>
 * 
 * @since 1.0.0
 * @author Ghwolf
 */
public class LazyLoadingDeleteInfo extends AbstractDeleteInfo {

	/**
	 * 寻找where正则表达式，忽略大小写
	 */
	protected static final Pattern WHERE_PATTERN = Pattern.compile("\\s+where\\s+", Pattern.CASE_INSENSITIVE);
	/**
	 * 寻找delete from的正则表达式，忽略大小写
	 */
	protected static final Pattern DELETE_FROM_PATTERN = Pattern.compile("^delete\\s+from", Pattern.CASE_INSENSITIVE);
	/**
	 * MappedStatement类的SqlSource属性Field对象
	 */
	private static final Field SQL_SOURCE_FIELD;
	static {
		try {
			SQL_SOURCE_FIELD = MappedStatement.class.getDeclaredField("sqlSource");
			SQL_SOURCE_FIELD.setAccessible(true);
		} catch (NoSuchFieldException e) {
			throw new UnsupportedOperationException("当前mybatis版本不支持DeleteHelper操作，因为MappedStatement类没有sqlSource属性！", e);
		} catch (SecurityException e) {
			throw new UnsupportedOperationException("存在安全管理器，无法反射操作！", e);
		}
	}
	/**
	 * where子句
	 */
	private final String whereSql;
	/**
	 * 操作的表名称
	 */
	private final String tableName;
	/**
	 * 原始BoundSql类对象
	 */
	private BoundSql boundSql;
	/**
	 * 可以取得拦截方法信息和参数的对象
	 */
	private Invocation invocation;
	/**
	 * 包含了要执行的sql的大部分信息的对象。
	 */
	private MappedStatement ms;
	/**
	 * 原始执行sql语句传递的参数
	 */
	private Object parameterObject;
	/**
	 * 要删除的对象的信息，初始为null，再调用的时候需要到数据库去查询并赋值。
	 */
	private List<Map<String, Object>> result;
	/**
	 * 要删除的对象的信息（Bean的形式）
	 */
	private List<?> resultBean;

	/**
	 * Executor类对象，拦截方法的对象
	 */
	private Executor executor;

	/**
	 * 实例化本类对象的时候，只需要将拦截方法的参数传入即可。
	 * 
	 * @param invocation Invocation类对象
	 */
	public LazyLoadingDeleteInfo(Invocation invocation) {
		this.invocation = invocation;
		this.executor = (Executor) invocation.getTarget();
		this.ms = (MappedStatement) invocation.getArgs()[0];
		this.parameterObject = invocation.getArgs()[1];
		this.boundSql = ms.getBoundSql(this.parameterObject);
		String sql = this.boundSql.getSql();

		// 解析delete的sql中的where子句和表名称
		sql = sql.trim();
		Matcher matcher = DELETE_FROM_PATTERN.matcher(sql);
		int deleteFromEnd;
		if (matcher.find()) {
			deleteFromEnd = matcher.end();
		} else {
			throw new UnsupportedOperationException("不是一个有效的delete语句：\n" + sql + "\n");
		}
		matcher = WHERE_PATTERN.matcher(sql);
		int whereStart;
		if (matcher.find()) {
			whereStart = matcher.start();
			this.whereSql = sql.substring(whereStart);
		} else {
			whereStart = sql.length();
			this.whereSql = "";
		}
		this.tableName = sql.substring(deleteFromEnd, whereStart);
	}

	/**
	 * 本方法负责取得被删除的数据信息集合.<br>
	 * 注意，只有在调用此方法的时候，才会发出查询语句，而且是查询全部字段。
	 * 
	 * @return 返回原始Map形式的数据集合，如果你想要以bean的形式返回，可以调用{@link #getDeleteBean(Class)}
	 *         方法
	 */
	@Override
	public List<Map<String, Object>> getDeleteInfo() throws SQLException {
		if (this.result == null) {
			String sql = "SELECT * FROM " + LazyLoadingDeleteInfo.this.tableName + " "
					+ LazyLoadingDeleteInfo.this.whereSql;
			final MappedStatement selectMs = super.getSelectMappedStatement(this.ms,
					new StaticSqlSource(ms.getConfiguration(), sql, this.getBoundSql().getParameterMappings()));

			// 修改MappedStatement为新的MappedStatement
			Object param = this.parameterObject;
			Map<String, Object> additionalParameter = this.getAdditionParameter();
			if (!additionalParameter.isEmpty()) {
				param = super.getMultipartObjectWrapper(additionalParameter);
			}
			this.result = this.executor.query(selectMs, param, RowBounds.DEFAULT, null);
			return this.result;
		} else {
			return this.result;
		}
	}

	/**
	 * 本方法负责取得被删除的数据信息集合并转换为bean.<br>
	 * 注意，只有在调用此方法的时候，才会发出查询语句，而且是查询全部字段。<br>
	 * 本方法相对{@link #getDeleteInfo()}来说性能较差，如果你不需要非得转换为bean，你可以调用
	 * {@link #getDeleteInfo()}方法。
	 * 
	 * @param beanClass 要转换的成为的bean的类型（POJO类型），必须是{@link Serializable}
	 *            接口的子类
	 * @return 将所有的数据转换为bean的形式返回。
	 *         需要注意的是，bean属性的名称可以和数据库列相同，也可以采用驼峰命名法，本方法会自动将数据库列名称转换为驼峰命名法去匹配属性，
	 *         如果没有找到，则不忽略跳过。
	 * @param <T> 要转换成为的POJO类型
	 */
	@Override
	@SuppressWarnings("unchecked")
	public <T> List<T> getDeleteBean(Class<T> beanClass) throws SQLException {
		Assert.notNull(beanClass, "bean的类型不能为null");
		if (this.resultBean == null) {
			List<Map<String, Object>> info = this.getDeleteInfo();
			List<T> beans = new ArrayList<>(info.size());

			try {
				for (Map<String, Object> map : info) {
					T obj;
						obj = beanClass.newInstance();
					MetaObject meta = this.ms.getConfiguration().newMetaObject(obj);
					for (Map.Entry<String, Object> entry : map.entrySet()) {
						// 要把数据库列名称转换为驼峰命名法
						String fieldName = GStringUtils.toCamelCase(entry.getKey());
						if (meta.hasSetter(fieldName)) {
							meta.setValue(fieldName, entry.getValue());
						}
					}
					beans.add(obj);
				}
			} catch (Exception e) {
				throw new ReflectionException(e);
			}
			this.resultBean = beans;
			return beans;
		} else {
			return (List<T>) this.resultBean;
		}
	}

	@Override
	public int updateColumn(@Nullable Map<String, Object> editParam) throws InvocationTargetException, IllegalAccessException {
		if (editParam == null || editParam.size() == 0) {
			return 0;
		}
		SqlSourceParamVo sqlSourceParam = super.createUpdateSqlSource(editParam);

		// 重新设置sqlSource和参数
		Object ss = SQL_SOURCE_FIELD.get(this.ms);
		SQL_SOURCE_FIELD.set(this.ms, sqlSourceParam.getSqlSource());
		this.invocation.getArgs()[1] = sqlSourceParam.getParam();
		super.changeCommentType(this.ms, SqlCommandType.UPDATE);
		int i = (int) this.invocation.proceed();
		// 还原sqlSource和参数
		SQL_SOURCE_FIELD.set(this.ms, ss);
		this.invocation.getArgs()[1] = this.parameterObject;
		super.changeCommentType(this.ms, SqlCommandType.DELETE);
		return i;
	}

	@Override
	public String getWhere() {
		return this.whereSql;
	}

	@Override
	public String getTableName() {
		return this.tableName;
	}

	@Override
	protected BoundSql getBoundSql() {
		return this.boundSql;
	}

	/**
	 * 执行原sql语句.<br>
	 * 执行原sql语句使用的是原有的MappedStatement、SqlSource和参数.<br>
	 * 执行完毕之后，会将参数设置为之前设置的参数
	 * 
	 * @return 返回删除的数据行数
	 */
	@Override
	public int proceed() throws InvocationTargetException, IllegalAccessException {
		Object[] arr = this.invocation.getArgs();
		arr[0] = this.ms;
		arr[1] = this.parameterObject;
		return (int) this.invocation.proceed();
	}

	@Override
	protected MappedStatement getMappedStatement() {
		return this.ms;
	}

	@Override
	protected Object getOriginalParam() {
		return this.parameterObject;
	}

	@Override
	protected Executor getExecutor() {
		return this.executor;
	}

}
